/* bs_account.c : bbs account operation
 * CopyRight (C) 2008 dgnbbs
 * drangon_zhou@21cn.com
 * 2008-10
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "bs_account.h"
#include "bs_system.h"

#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <crypt.h>

/* dest must be BBS_PASSLEN */
static int bbs_crypt( char * dest, const char * src )
{
	char salt[13];
	uint64_t tmp;
	int i;
	struct timeval tv;
	char * ret;
	static const char * s_table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
	
	gettimeofday( &tv, NULL );
	tmp = (uint64_t)rand() * ((uint64_t)rand() + tv.tv_sec) * tv.tv_usec;
	strncpy( salt, "$1$", 3 );
	for( i = 0; i < 8; i++ ) {
		salt[i + 3] = s_table[ tmp & 0x3f ];
		tmp = (tmp >> 6);
	}
	salt[11] = '$';
	salt[12] = '\0';
	ret = crypt( src, salt );
	strncpy( dest, ret, BBS_PASSLEN - 1 );
	return 0;
}

int32_t bs_account_new( const char * userid, const char * passwd )
{
	os_file_t fd;
	int i, idx, isfind;
	struct bs_account_t acct;
	struct bs_ucache_t * uc = bs_shm_ucache();

	/* <1> init account */
	memset( &acct, 0, sizeof(acct) );
	strncpy( acct.userid, userid, BBS_IDLEN );
	bbs_crypt( acct.passwd, passwd );
	acct.userlevel = BBS_PERM_BASIC;
	acct.firstlogin = acct.lastlogin = time(NULL);

	/* <2> create account */
	if( (fd = os_file_open( BBS_FILE_PASSWDS )) < 0 ) {
		return -1;
	}
	os_file_lock( fd );
	isfind = 0;
	idx = -1;
	for( i = 0; i < BBS_MAXUSER; i++ ) {
		if( uc->userid[i][0] == '\0' ) {
			if( idx != -1 )
				idx = i;
		}
		// TODO : portable, strcasecmp
		else if( strcasecmp( uc->userid[i], userid ) == 0 ) {
			isfind = 1;
			break;
		}
	}
	if( isfind == 1 ) {
		os_file_unlock( fd );
		os_file_close( fd );
		return ERR_ACCOUNT_NEW_EXIST;
	}
	if( idx == -1 ) {
		os_file_unlock( fd );
		os_file_close( fd );
		return ERR_ACCOUNT_NEW_FULL;
	}
	if( os_file_write( fd, sizeof(acct) * idx, (char *)&acct, sizeof(acct) ) < 0 ) {
		os_file_unlock( fd );
		os_file_close( fd );
		return -1;
	}
	strncpy( uc->userid[idx], userid, BBS_IDLEN );
	uc->number += 1;
	os_file_unlock( fd );
	os_file_close( fd );
	return 0;
}

int32_t bs_account_find( const char * userid )
{
	int i;
	struct bs_ucache_t * uc = bs_shm_ucache();
	// TODO : use hash to speed up find
	for( i = 0; i < uc->number; i++ ) {
		if( strcasecmp( userid, uc->userid[i] ) == 0 )
			return i;
	}
	return -1;
}

int bs_account_load( int32_t uid, struct bs_account_t * acct_info )
{
	int ret;
	os_file_t fd;
	if( uid < 0 || uid >= BBS_MAXUSER ) {
		return -1;
	}
	/* TODO : lock between load/save */
	if( (fd = os_file_open( BBS_FILE_PASSWDS )) == OS_FILE_INVALID_HANDLE ) {
		return -1;
	}
	ret = os_file_read( fd, sizeof(*acct_info) * uid, (char *)acct_info, sizeof(*acct_info) );
	os_file_close( fd );
	return ret;
}

int bs_account_save( int32_t uid, const struct bs_account_t * acct_info )
{
	int ret;
	os_file_t fd;
	struct bs_ucache_t * uc = bs_shm_ucache();
	if( uid < 0 || uid >= BBS_MAXUSER ) {
		return -1;
	}
	if( strcmp( acct_info->userid, uc->userid[uid] ) != 0 ) {
		return -1;
	}
	/* TODO : lock between load/save */
	if( (fd = os_file_open( BBS_FILE_PASSWDS )) == OS_FILE_INVALID_HANDLE ) {
		return -1;
	}
	ret = os_file_write( fd, sizeof(*acct_info) * uid, (char *)acct_info, sizeof(*acct_info) );
	os_file_close( fd );
	return ret;
} 

int bs_account_setpass( struct bs_account_t * acct_info, const char * newpass )
{
	bbs_crypt( acct_info->passwd, newpass );
	return 0;
}

int bs_account_checkpass( const struct bs_account_t * acct_info, const char * passwd )
{
	char crypt_pass[BBS_PASSLEN];
	memset( crypt_pass, 0, sizeof(crypt_pass) );
	bbs_crypt( crypt_pass, passwd );
	if( strcmp( crypt_pass, acct_info->passwd ) == 0 )
		return 1;
	return 0;
}

